package sk.stuba.fiit.pki.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.UnrecoverableEntryException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import sk.stuba.fiit.pki.test.InitializationUtil;

public class CertificateManager {

	private final static int ONE_DAY = 24*60*60*1000;
	
	private static CertificateManager manager;
	
	public static CertificateManager getInstance(){
		if (manager == null){
			return new CertificateManager();
		}
		return manager;
	}
	
	private CertificateManager() {
		manager = this;
	}

	public  X509Certificate generateRootSelfSignedCertificate(KeyPair pair,BigInteger serialNumber)
			throws Exception {
		X509V3CertificateGenerator x509v3certificategenerator = new X509V3CertificateGenerator();
		x509v3certificategenerator.setSerialNumber(serialNumber);
		x509v3certificategenerator.setIssuerDN(new X509Name(InitializationUtil.getCertificateProperties().getProperty("R_setIssuerDN")));
		x509v3certificategenerator.setNotBefore(new Date(System.currentTimeMillis()));
		int tmp = Integer.valueOf(InitializationUtil.getCertificateProperties().getProperty("R_setNotAfter"));
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.add(Calendar.DATE, tmp);
		x509v3certificategenerator.setNotAfter(calendar.getTime());
		x509v3certificategenerator.setSubjectDN(new X509Name(InitializationUtil.getCertificateProperties().getProperty("R_setSubjectDN")));
		x509v3certificategenerator.setPublicKey(pair.getPublic());
		x509v3certificategenerator.setSignatureAlgorithm(InitializationUtil.getCertificateProperties().getProperty("R_setSignatureAlgorithm"));		
		x509v3certificategenerator.addExtension(X509Extensions.BasicConstraints, false,	new BasicConstraints(true));	
		X509Certificate x509certificate = x509v3certificategenerator.generate(pair.getPrivate(),"BC");
		x509certificate.checkValidity(new Date());
		x509certificate.verify(pair.getPublic());
		return x509certificate;
	}
	
	public  X509Certificate generateCertificateAuthorityCertificate(PublicKey intKey, PrivateKey caKey, X509Certificate caCert,BigInteger serialNumber) throws Exception{
		  	X509V3CertificateGenerator x509v3certificategenerator = new X509V3CertificateGenerator();
		      x509v3certificategenerator.setSerialNumber(serialNumber);
		      x509v3certificategenerator.setIssuerDN(caCert.getSubjectX500Principal());
		      x509v3certificategenerator.setNotBefore(new Date(System.currentTimeMillis()));
		      int tmp = Integer.valueOf(InitializationUtil.getCertificateProperties().getProperty("CA_setNotAfter"));
		      GregorianCalendar calendar = new GregorianCalendar();
				calendar.add(Calendar.DATE, tmp);
				x509v3certificategenerator.setNotAfter(calendar.getTime());
		      x509v3certificategenerator.setSubjectDN( new X509Name(InitializationUtil.getCertificateProperties().getProperty("CA_setSubjectDN")));
		      x509v3certificategenerator.setPublicKey(intKey);
		      x509v3certificategenerator.setSignatureAlgorithm(InitializationUtil.getCertificateProperties().getProperty("CA_setSignatureAlgorithm"));
		      x509v3certificategenerator.addExtension(X509Extensions.AuthorityKeyIdentifier,false, new AuthorityKeyIdentifierStructure(caCert));
		      x509v3certificategenerator.addExtension(X509Extensions.SubjectKeyIdentifier,false, new SubjectKeyIdentifierStructure(intKey));
	 	      x509v3certificategenerator.addExtension(X509Extensions.BasicConstraints,true, new BasicConstraints(0));
	 	     
	 	      x509v3certificategenerator.addExtension( X509Extensions.KeyUsage, true, new KeyUsage( KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign));
			x509v3certificategenerator.addExtension(X509Extensions.ExtendedKeyUsage, true,new ExtendedKeyUsage(new DERSequence(KeyPurposeId.id_kp_timeStamping)));			
			X509Certificate newCert = x509v3certificategenerator.generateX509Certificate(caKey, "BC");
			newCert.checkValidity(new Date());
			newCert.verify(caCert.getPublicKey());
			return newCert;
	}
	
	

	public  X509Certificate generateEndEntityCert(PublicKey entityKey, PrivateKey caKey, X509Certificate caCert,BigInteger serialNumber)  throws Exception {
		      X509V3CertificateGenerator x509v3certificategenerator = new X509V3CertificateGenerator();
		      x509v3certificategenerator.setSerialNumber(serialNumber);
		      x509v3certificategenerator.setIssuerDN(caCert.getSubjectX500Principal());
		      x509v3certificategenerator.setNotBefore(new Date(System.currentTimeMillis()));
		      int tmp= Integer.valueOf(InitializationUtil.getCertificateProperties().getProperty("E_setNotAfter"));
		      GregorianCalendar calendar = new GregorianCalendar();
				calendar.add(Calendar.DATE, tmp);
				x509v3certificategenerator.setNotAfter(calendar.getTime());
		      x509v3certificategenerator.setSubjectDN(new X509Name(InitializationUtil.getCertificateProperties().getProperty("E_setSubjectDN")));
		      x509v3certificategenerator.setPublicKey(entityKey);
		      x509v3certificategenerator.setSignatureAlgorithm(InitializationUtil.getCertificateProperties().getProperty("E_setSignatureAlgorithm"));
		      x509v3certificategenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(caCert));
		      x509v3certificategenerator.addExtension(X509Extensions.SubjectKeyIdentifier,false, new SubjectKeyIdentifierStructure(entityKey));
		      x509v3certificategenerator.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
		      x509v3certificategenerator.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
		      x509v3certificategenerator.addExtension(X509Extensions.ExtendedKeyUsage, true,new ExtendedKeyUsage(new DERSequence(KeyPurposeId.id_kp_timeStamping)));
		      X509Certificate newCert = x509v3certificategenerator.generateX509Certificate(caKey, "BC");
			newCert.checkValidity(new Date());
			newCert.verify(caCert.getPublicKey());
			return newCert;
		   }
	
	public KeyPair generateKeyPair(String algorithm, int lenght)
			throws NoSuchAlgorithmException, NoSuchProviderException {
		SecureRandom rand = new SecureRandom();
		if (algorithm == null) {
			algorithm = "RSA";
		}
		KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm, "BC");
		kpg.initialize(1024, rand);
		return kpg.generateKeyPair();
	}
	
	public PrivateKey loadPrivateKey(String storageType, String fileName,
			String password) throws KeyStoreException, IOException,
			NoSuchAlgorithmException, CertificateException,
			UnrecoverableKeyException {
		KeyStore ks = KeyStore.getInstance(storageType);
		FileInputStream fis = new FileInputStream(new File(fileName));
		ks.load(fis, password.toCharArray());
		fis.close();
		PrivateKey key = (PrivateKey) ks
				.getKey("alias", password.toCharArray());
		return key;
	}
	
	public boolean storeCertificateWithPrivateKey(File file,PrivateKey key,X509Certificate[] chain,String password){
		try {
			if(!file.exists())
				file.createNewFile();
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(null, null);		
			KeyStore.PrivateKeyEntry privateKeyEntry = new KeyStore.PrivateKeyEntry(key,chain);
			ks.setEntry("alias", privateKeyEntry, new KeyStore.PasswordProtection(password.toCharArray()));
			FileOutputStream fos=  new FileOutputStream(file);
			ks.store(fos, password.toCharArray());
			fos.close();
			return true;
		}catch(Exception e){
			e.printStackTrace();
			return false;
		}		
	}
	
	public X509Certificate loadCertificate(String fileName, String password,String storageType) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableEntryException{
		if(storageType==null)
			storageType="JKS";
		KeyStore ks = KeyStore.getInstance(storageType);
		FileInputStream fis = new FileInputStream(new File(fileName));
		if(password==null){
			ks.load(fis,null);
		}else{
			ks.load(fis,password.toCharArray());
		}
		fis.close();
		X509Certificate cert = (X509Certificate) ks.getCertificate("alias");
		return cert;
	}
	
	public X509Certificate makeCertificate(PublicKey publickey, PrivateKey privatekey,
			X509Certificate cert_CA, boolean flag, BigInteger allocateCertSerialNumber) throws GeneralSecurityException, IOException {
		JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils();
		X509V3CertificateGenerator x509v3certificategenerator = new X509V3CertificateGenerator();
		x509v3certificategenerator.reset();
		x509v3certificategenerator.setSerialNumber(allocateCertSerialNumber);
		x509v3certificategenerator.setIssuerDN(cert_CA.getSubjectX500Principal());
		x509v3certificategenerator.setNotBefore(new Date(System.currentTimeMillis()));
		int tmp= Integer.valueOf(InitializationUtil.getCertificateProperties().getProperty("TSA_setNotAfter"));
		GregorianCalendar calendar = new GregorianCalendar();
		calendar.add(Calendar.DATE, tmp);
		x509v3certificategenerator.setNotAfter(calendar.getTime());
		x509v3certificategenerator.setSubjectDN(new X509Name(InitializationUtil.getCertificateProperties().getProperty("TSA_setSubjectDN")));
		x509v3certificategenerator.setPublicKey(publickey);
		x509v3certificategenerator.setSignatureAlgorithm("SHA1WithRSAEncryption");
		x509v3certificategenerator.addExtension(X509Extensions.SubjectKeyIdentifier, false,	extUtils.createSubjectKeyIdentifier(publickey));
		x509v3certificategenerator.addExtension(X509Extensions.AuthorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(publickey));
		if (flag)
			x509v3certificategenerator.addExtension(X509Extensions.BasicConstraints, false, new BasicConstraints(flag));
		else
			x509v3certificategenerator.addExtension(X509Extensions.ExtendedKeyUsage, true, new ExtendedKeyUsage(new DERSequence(KeyPurposeId.id_kp_timeStamping)));
		X509Certificate x509certificate = x509v3certificategenerator.generate(privatekey);
		x509certificate.checkValidity(new Date());
		x509certificate.verify(cert_CA.getPublicKey());
		return x509certificate;
	}
	
	
	public boolean generateAndSaveKeyPair(String path, String algorithm,
			int lenght,String privateKeyName, String publicKeyName) throws NoSuchAlgorithmException,
			NoSuchProviderException {
		SecureRandom rand = new SecureRandom();
		if (algorithm == null) {
			algorithm = "RSA";
		}
		KeyPairGenerator kpg = KeyPairGenerator.getInstance(algorithm, "BC");
		kpg.initialize(1024, rand);
		try {
			saveKeyPair(path, kpg.generateKeyPair(),publicKeyName,privateKeyName);
			return true;
		} catch (IOException e) {
			e.printStackTrace();
			return false;
		}
	}


public void saveKeyPair(String path, KeyPair keyPair,String namePublicKey, String namePrivateKey) throws IOException {
		PrivateKey privateKey = keyPair.getPrivate();
		PublicKey publicKey = keyPair.getPublic();
		
		if(namePublicKey==null){
			namePublicKey = "public.key";
		}
		if(namePrivateKey==null){
			namePrivateKey = "private.key";
		}

		X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(
				publicKey.getEncoded());
		FileOutputStream fos = new FileOutputStream(path + File.separator
				+ namePublicKey);
		fos.write(x509EncodedKeySpec.getEncoded());
		fos.close();

		PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(
				privateKey.getEncoded());
		fos = new FileOutputStream(path + File.separator + namePrivateKey);
		fos.write(pkcs8EncodedKeySpec.getEncoded());
		fos.close();
	}


public KeyPair loadKeyPair(String path, String algorithm,String publicKeyName, String privateKeyName)
			throws IOException, NoSuchAlgorithmException,
			InvalidKeySpecException {

		if(publicKeyName == null){
			publicKeyName = "public.key";
		}
		if(privateKeyName==null){
			privateKeyName="private.key";
		}
		
		if (algorithm == null) {
			algorithm = "RSA";
		}
		File filePublicKey = new File(path + File.separator + publicKeyName);
		FileInputStream fis = new FileInputStream(filePublicKey);
		byte[] encodedPublicKey = new byte[(int) filePublicKey.length()];
		fis.read(encodedPublicKey);
		fis.close();

		File filePrivateKey = new File(path + File.separator + privateKeyName);
		fis = new FileInputStream(filePrivateKey);
		byte[] encodedPrivateKey = new byte[(int) filePrivateKey.length()];
		fis.read(encodedPrivateKey);
		fis.close();

		KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
		X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(
				encodedPublicKey);
		PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);

		PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(
				encodedPrivateKey);
		PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

		return new KeyPair(publicKey, privateKey);
	}


}

